我們要測試四件事:
import { renderHook, act } from "@testing-library/react";
import { useDebouncedCallback } from "../src"; // 請根據您的檔案結構進行調整
jest.useFakeTimers(); // 使用 Jest 的模擬定時器
describe("useDebouncedCallback hook", () => {
//應該要到設定的時間才執行
it("should call the callback function after the specified delay", () => {
const callback = jest.fn(); // 創建一個模擬的回調函數
const delay = 300; // 300ms 的延遲
const { result } = renderHook(() => useDebouncedCallback(callback, delay));
act(() => {
result.current(); // 調用 debounced 函數
});
expect(callback).not.toHaveBeenCalled(); // 在延遲時間到達前,不應該調用回調函數
act(() => {
jest.advanceTimersByTime(delay); // 快進定時器來觸發回調
});
expect(callback).toHaveBeenCalled(); // 在延遲時間到達後,應該調用回調函數
});
// 測試重複調用
it("should only call the callback after the last invocation within the delay", () => {
const callback = jest.fn();
const delay = 300;
const { result } = renderHook(() => useDebouncedCallback(callback, delay));
act(() => {
result.current(); // 第一次調用
jest.advanceTimersByTime(100); // 前進 100ms
result.current(); // 在延遲時間內的第二次調用
});
expect(callback).not.toHaveBeenCalled(); // 延遲時間內不應該調用回調
act(() => {
jest.advanceTimersByTime(delay); // 前進到最後一次調用後的 300ms
});
expect(callback).toHaveBeenCalledTimes(1); // 最終應該只調用一次回調
});
// 測試參數傳遞
it("should pass the correct arguments to the callback", () => {
const callback = jest.fn();
const delay = 300;
const { result } = renderHook(() => useDebouncedCallback(callback, delay));
act(() => {
result.current("arg1", "arg2"); // 調用 debounced 函數並傳遞參數
jest.advanceTimersByTime(delay); // 前進定時器觸發回調
});
expect(callback).toHaveBeenCalledWith("arg1", "arg2"); // 應該使用正確的參數調用回調
});
// 測試清理
it("should clean up the timer when the component is unmounted", () => {
const callback = jest.fn();
const delay = 300;
const { result, unmount } = renderHook(() =>
useDebouncedCallback(callback, delay)
);
act(() => {
result.current(); // 調用 debounced 函數
});
act(() => {
unmount(); // 卸載組件
});
act(() => {
jest.advanceTimersByTime(delay); // 前進定時器
});
expect(callback).not.toHaveBeenCalled(); // 卸載後不應該調用回調
});
});
advanceTimersByTime
可以模擬快進,跟上次直接完成不一樣,也是一個很方便使用的功能
如果複製貼上這段測試碼,就會發現測試清理這個測試沒有通過,回去看useDebouncedCallback
,我發現我沒有useEffect
清除timeout
如果卸載組件
加上這個就沒問題了
useEffect(() => {
return () => {
if (timerIdRef.current !== null) {
clearTimeout(timerIdRef.current);
}
};
}, []);
useDebouncedCallback
完整程式碼:
import { useRef, useCallback, useEffect } from "react";
export function useDebouncedCallback<T extends (...args: any[]) => any>(
callback: T,
delay: number
) {
const timerIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const debouncedCallback = useCallback(
(...args: Parameters<T>) => {
if (timerIdRef.current !== null) {
clearTimeout(timerIdRef.current);
}
timerIdRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay]
);
useEffect(() => {
return () => {
if (timerIdRef.current !== null) {
clearTimeout(timerIdRef.current);
}
};
}, []);
return debouncedCallback;
}